<!DOCTYPE html>
<html lang=zh>
<head>
  <meta charset="utf-8">
  
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui">
  <meta name="renderer" content="webkit">
  <meta http-equiv="Cache-Control" content="no-transform" />
  <meta http-equiv="Cache-Control" content="no-siteapp" />
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="format-detection" content="telephone=no,email=no,adress=no">
  <!-- Color theme for statusbar -->
  <meta name="theme-color" content="#000000" />
  <!-- 强制页面在当前窗口以独立页面显示,防止别人在框架里调用页面 -->
  <meta http-equiv="window-target" content="_top" />
  
  
  <title>Netty聊天室 | Skyung-Blog</title>
  <meta name="description" content="基于netty实现聊天室的功能，单聊和群聊，控制台效果无界面 1 整体框架服务端和客户端1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162&#x2F;&#x2F;服务端public class ChatServer &amp;#123;    pu">
<meta property="og:type" content="article">
<meta property="og:title" content="Netty聊天室">
<meta property="og:url" content="https://skyung.gitee.io/Skyung/2022/05/01/netty%E8%81%8A%E5%A4%A9%E5%AE%A4/index.html">
<meta property="og:site_name" content="Skyung-Blog">
<meta property="og:description" content="基于netty实现聊天室的功能，单聊和群聊，控制台效果无界面 1 整体框架服务端和客户端1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162&#x2F;&#x2F;服务端public class ChatServer &amp;#123;    pu">
<meta property="og:locale" content="zh_CN">
<meta property="article:published_time" content="2022-04-30T16:00:00.000Z">
<meta property="article:modified_time" content="2022-04-30T16:00:00.000Z">
<meta property="article:author" content="Skyung">
<meta property="article:tag" content="java">
<meta name="twitter:card" content="summary">
  <!-- Canonical links -->
  <link rel="canonical" href="https://skyung.gitee.io/Skyung/2022/05/01/netty%E8%81%8A%E5%A4%A9%E5%AE%A4/index.html">
  
    <link rel="alternate" href="/atom.xml" title="Skyung-Blog" type="application/atom+xml">
  
  
    <link rel="icon" href="images/favicon.png" type="image/x-icon">
  
  
<link rel="stylesheet" href="/Skyung/css/style.css">

  
  
  
  
<meta name="generator" content="Hexo 5.4.1"></head>


<body class="main-center" itemscope itemtype="http://schema.org/WebPage">
  <header class="header" itemscope itemtype="http://schema.org/WPHeader">
  <div class="slimContent">
    <div class="navbar-header">
      
      
      <div class="profile-block text-center">
        <a id="avatar" href="https://github.com/cofess" target="_blank">
          <img class="img-circle img-rotate" src="/Skyung/images/avatar.jpg" width="200" height="200">
        </a>
        <h2 id="name" class="hidden-xs hidden-sm">Skyung</h2>
        <h3 id="title" class="hidden-xs hidden-sm hidden-md">Web Developer</h3>
        <small id="location" class="text-muted hidden-xs hidden-sm"><i class="icon icon-map-marker"></i> Wuhan, China</small>
      </div>
      
      <div class="search" id="search-form-wrap">

    <form class="search-form sidebar-form">
        <div class="input-group">
            <input type="text" class="search-form-input form-control" placeholder="搜索" />
            <span class="input-group-btn">
                <button type="submit" class="search-form-submit btn btn-flat" onclick="return false;"><i class="icon icon-search"></i></button>
            </span>
        </div>
    </form>
    <div class="ins-search">
  <div class="ins-search-mask"></div>
  <div class="ins-search-container">
    <div class="ins-input-wrapper">
      <input type="text" class="ins-search-input" placeholder="想要查找什么..." x-webkit-speech />
      <button type="button" class="close ins-close ins-selectable" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
    </div>
    <div class="ins-section-wrapper">
      <div class="ins-section-container"></div>
    </div>
  </div>
</div>


</div>
      <button class="navbar-toggle collapsed" type="button" data-toggle="collapse" data-target="#main-navbar" aria-controls="main-navbar" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
    </div>
    <nav id="main-navbar" class="collapse navbar-collapse" itemscope itemtype="http://schema.org/SiteNavigationElement" role="navigation">
      <ul class="nav navbar-nav main-nav ">
        
        
        <li class="menu-item menu-item-home">
          <a href="/Skyung/.">
            
            <i class="icon icon-home-fill"></i>
            
            <span class="menu-title">首页</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-archives">
          <a href="/Skyung/archives">
            
            <i class="icon icon-archives-fill"></i>
            
            <span class="menu-title">归档</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-categories">
          <a href="/Skyung/categories">
            
            <i class="icon icon-folder"></i>
            
            <span class="menu-title">分类</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-tags">
          <a href="/Skyung/tags">
            
            <i class="icon icon-tags"></i>
            
            <span class="menu-title">标签</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-repository">
          <a href="/Skyung/repository">
            
            <i class="icon icon-project"></i>
            
            <span class="menu-title">项目</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-books">
          <a href="/Skyung/books">
            
            <i class="icon icon-book-fill"></i>
            
            <span class="menu-title">书单</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-links">
          <a href="/Skyung/links">
            
            <i class="icon icon-friendship"></i>
            
            <span class="menu-title">友链</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-about">
          <a href="/Skyung/about">
            
            <i class="icon icon-cup-fill"></i>
            
            <span class="menu-title">关于</span>
          </a>
        </li>
        
      </ul>
      
	
    <ul class="social-links">
    	
        <li><a href="/Skyung/null" target="_blank" title="Github" data-toggle=tooltip data-placement=top><i class="icon icon-github"></i></a></li>
        
        <li><a href="/Skyung/null" target="_blank" title="Weibo" data-toggle=tooltip data-placement=top><i class="icon icon-weibo"></i></a></li>
        
        <li><a href="/Skyung/null" target="_blank" title="Twitter" data-toggle=tooltip data-placement=top><i class="icon icon-twitter"></i></a></li>
        
        <li><a href="/Skyung/null" target="_blank" title="Behance" data-toggle=tooltip data-placement=top><i class="icon icon-behance"></i></a></li>
        
        <li><a href="/Skyung/atom.xml" target="_blank" title="Rss" data-toggle=tooltip data-placement=top><i class="icon icon-rss"></i></a></li>
        
    </ul>

    </nav>
  </div>
</header>

  
    <aside class="sidebar" itemscope itemtype="http://schema.org/WPSideBar">
  <div class="slimContent">
    
      <div class="widget">
    <h3 class="widget-title">公告</h3>
    <div class="widget-body">
        <div id="board">
            <div class="content">
                <p>欢迎交流与分享经验!</p>
            </div>
        </div>
    </div>
</div>

    
      
  <div class="widget">
    <h3 class="widget-title">分类</h3>
    <div class="widget-body">
      <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/Skyung/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">学习笔记</a><span class="category-list-count">20</span></li></ul>
    </div>
  </div>


    
      
  <div class="widget">
    <h3 class="widget-title">标签</h3>
    <div class="widget-body">
      <ul class="tag-list" itemprop="keywords"><li class="tag-list-item"><a class="tag-list-link" href="/Skyung/tags/java/" rel="tag">java</a><span class="tag-list-count">19</span></li><li class="tag-list-item"><a class="tag-list-link" href="/Skyung/tags/jvm/" rel="tag">jvm</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/Skyung/tags/linux/" rel="tag">linux</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/Skyung/tags/redis/" rel="tag">redis</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/Skyung/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/" rel="tag">多线程</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/Skyung/tags/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/" rel="tag">并发编程</a><span class="tag-list-count">2</span></li><li class="tag-list-item"><a class="tag-list-link" href="/Skyung/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" rel="tag">设计模式</a><span class="tag-list-count">4</span></li><li class="tag-list-item"><a class="tag-list-link" href="/Skyung/tags/%E9%9B%B6%E6%95%A3%E7%82%B9/" rel="tag">零散点</a><span class="tag-list-count">1</span></li></ul>
    </div>
  </div>


    
      
  <div class="widget">
    <h3 class="widget-title">标签云</h3>
    <div class="widget-body tagcloud">
      <a href="/Skyung/tags/java/" style="font-size: 14px;">java</a> <a href="/Skyung/tags/jvm/" style="font-size: 13px;">jvm</a> <a href="/Skyung/tags/linux/" style="font-size: 13px;">linux</a> <a href="/Skyung/tags/redis/" style="font-size: 13px;">redis</a> <a href="/Skyung/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/" style="font-size: 13px;">多线程</a> <a href="/Skyung/tags/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/" style="font-size: 13.33px;">并发编程</a> <a href="/Skyung/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" style="font-size: 13.67px;">设计模式</a> <a href="/Skyung/tags/%E9%9B%B6%E6%95%A3%E7%82%B9/" style="font-size: 13px;">零散点</a>
    </div>
  </div>

    
      
  <div class="widget">
    <h3 class="widget-title">归档</h3>
    <div class="widget-body">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/Skyung/archives/2022/05/">五月 2022</a><span class="archive-list-count">5</span></li><li class="archive-list-item"><a class="archive-list-link" href="/Skyung/archives/2022/04/">四月 2022</a><span class="archive-list-count">7</span></li><li class="archive-list-item"><a class="archive-list-link" href="/Skyung/archives/2022/03/">三月 2022</a><span class="archive-list-count">9</span></li><li class="archive-list-item"><a class="archive-list-link" href="/Skyung/archives/2022/02/">二月 2022</a><span class="archive-list-count">1</span></li><li class="archive-list-item"><a class="archive-list-link" href="/Skyung/archives/2021/09/">九月 2021</a><span class="archive-list-count">1</span></li></ul>
    </div>
  </div>


    
      
  <div class="widget">
    <h3 class="widget-title">最新文章</h3>
    <div class="widget-body">
      <ul class="recent-post-list list-unstyled no-thumbnail">
        
          <li>
            
            <div class="item-inner">
              <p class="item-category">
                <a class="category-link" href="/Skyung/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">学习笔记</a>
              </p>
              <p class="item-title">
                <a href="/Skyung/2022/05/08/Spring/" class="title">spring</a>
              </p>
              <p class="item-date">
                <time datetime="2022-05-07T16:00:00.000Z" itemprop="datePublished">2022-05-08</time>
              </p>
            </div>
          </li>
          
          <li>
            
            <div class="item-inner">
              <p class="item-category">
                <a class="category-link" href="/Skyung/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">学习笔记</a>
              </p>
              <p class="item-title">
                <a href="/Skyung/2022/05/08/netty-RPC/" class="title">Netty-RPC</a>
              </p>
              <p class="item-date">
                <time datetime="2022-05-07T16:00:00.000Z" itemprop="datePublished">2022-05-08</time>
              </p>
            </div>
          </li>
          
          <li>
            
            <div class="item-inner">
              <p class="item-category">
                <a class="category-link" href="/Skyung/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">学习笔记</a>
              </p>
              <p class="item-title">
                <a href="/Skyung/2022/05/07/%E5%B8%B8%E7%94%A8%E7%B1%BB%E5%BA%93%E5%92%8CAPI/" class="title">常见类库</a>
              </p>
              <p class="item-date">
                <time datetime="2022-05-06T16:00:00.000Z" itemprop="datePublished">2022-05-07</time>
              </p>
            </div>
          </li>
          
          <li>
            
            <div class="item-inner">
              <p class="item-category">
                <a class="category-link" href="/Skyung/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">学习笔记</a>
              </p>
              <p class="item-title">
                <a href="/Skyung/2022/05/07/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/" class="title">单元测试</a>
              </p>
              <p class="item-date">
                <time datetime="2022-05-06T16:00:00.000Z" itemprop="datePublished">2022-05-07</time>
              </p>
            </div>
          </li>
          
          <li>
            
            <div class="item-inner">
              <p class="item-category">
                <a class="category-link" href="/Skyung/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">学习笔记</a>
              </p>
              <p class="item-title">
                <a href="/Skyung/2022/05/01/netty%E8%81%8A%E5%A4%A9%E5%AE%A4/" class="title">Netty聊天室</a>
              </p>
              <p class="item-date">
                <time datetime="2022-04-30T16:00:00.000Z" itemprop="datePublished">2022-05-01</time>
              </p>
            </div>
          </li>
          
      </ul>
    </div>
  </div>
  

    
  </div>
</aside>

  
  
  <aside class="sidebar sidebar-toc collapse   in  " id="collapseToc" itemscope itemtype="http://schema.org/WPSideBar">
  <div class="slimContent">
    <nav id="toc" class="article-toc">
      <h3 class="toc-title">文章目录</h3>
      <ol class="toc"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-%E6%95%B4%E4%BD%93%E6%A1%86%E6%9E%B6"><span class="toc-number">1.</span> <span class="toc-text">1 整体框架</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%92%8C%E5%AE%A2%E6%88%B7%E7%AB%AF"><span class="toc-number">1.1.</span> <span class="toc-text">服务端和客户端</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BC%9A%E8%AF%9D%E7%AE%A1%E7%90%86"><span class="toc-number">1.2.</span> <span class="toc-text">会话管理</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%9B%B8%E5%85%B3%E7%B1%BB"><span class="toc-number">1.3.</span> <span class="toc-text">相关类</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%94%A8%E6%88%B7%E7%AE%A1%E7%90%86"><span class="toc-number">1.4.</span> <span class="toc-text">用户管理</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-%E8%81%8A%E5%A4%A9%E5%AE%A4%E4%B8%9A%E5%8A%A1-%E7%99%BB%E5%BD%95"><span class="toc-number">2.</span> <span class="toc-text">2 聊天室业务-登录</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-%E8%81%8A%E5%A4%A9%E5%AE%A4%E4%B8%9A%E5%8A%A1-%E5%8D%95%E8%81%8A"><span class="toc-number">3.</span> <span class="toc-text">3 聊天室业务-单聊</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-%E8%81%8A%E5%A4%A9%E5%AE%A4%E4%B8%9A%E5%8A%A1-%E7%BE%A4%E8%81%8A"><span class="toc-number">4.</span> <span class="toc-text">4 聊天室业务-群聊</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-%E8%81%8A%E5%A4%A9%E5%AE%A4%E4%B8%9A%E5%8A%A1-%E9%80%80%E5%87%BA"><span class="toc-number">5.</span> <span class="toc-text">5 聊天室业务-退出</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#6-%E8%81%8A%E5%A4%A9%E5%AE%A4%E4%B8%9A%E5%8A%A1-%E7%A9%BA%E9%97%B2%E6%A3%80%E6%B5%8B"><span class="toc-number">6.</span> <span class="toc-text">6 聊天室业务-空闲检测</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%BF%9E%E6%8E%A5%E5%81%87%E6%AD%BB"><span class="toc-number">6.1.</span> <span class="toc-text">连接假死</span></a></li></ol></li></ol>
    </nav>
  </div>
</aside>

<main class="main" role="main">
  <div class="content">
  <article id="post-netty聊天室" class="article article-type-post" itemscope itemtype="http://schema.org/BlogPosting">
    
    <div class="article-header">
      
        
  
    <h1 class="article-title" itemprop="name">
      Netty聊天室
    </h1>
  

      
      <div class="article-meta">
        <span class="article-date">
    <i class="icon icon-calendar-check"></i>
	<a href="/Skyung/2022/05/01/netty%E8%81%8A%E5%A4%A9%E5%AE%A4/" class="article-date">
	  <time datetime="2022-04-30T16:00:00.000Z" itemprop="datePublished">2022-05-01</time>
	</a>
</span>
        
  <span class="article-category">
    <i class="icon icon-folder"></i>
    <a class="article-category-link" href="/Skyung/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">学习笔记</a>
  </span>

        
  <span class="article-tag">
    <i class="icon icon-tags"></i>
	<a class="article-tag-link-link" href="/Skyung/tags/java/" rel="tag">java</a>
  </span>


        

        <span class="post-comment"><i class="icon icon-comment"></i> <a href="/Skyung/2022/05/01/netty%E8%81%8A%E5%A4%A9%E5%AE%A4/#comments" class="article-comment-link">评论</a></span>
        
      </div>
    </div>
    <div class="article-entry marked-body" itemprop="articleBody">
      
        <p>基于netty实现聊天室的功能，单聊和群聊，控制台效果无界面</p>
<h3 id="1-整体框架"><a href="#1-整体框架" class="headerlink" title="1 整体框架"></a>1 整体框架</h3><h4 id="服务端和客户端"><a href="#服务端和客户端" class="headerlink" title="服务端和客户端"></a>服务端和客户端</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//服务端</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChatServer</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">boss</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">worker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="type">MessageCodecSharable</span> <span class="variable">MESSAGE_CODEC</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MessageCodecSharable</span>();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">ServerBootstrap</span> <span class="variable">serverBootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServerBootstrap</span>();</span><br><span class="line">            serverBootstrap.channel(NioServerSocketChannel.class);</span><br><span class="line">            serverBootstrap.group(boss, worker);</span><br><span class="line">            serverBootstrap.childHandler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ProcotolFrameDecoder</span>());</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">MessageCodecSharable</span>());<span class="comment">//自定义的编解码器协议，见netty</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="type">Channel</span> <span class="variable">channel</span> <span class="operator">=</span> serverBootstrap.bind(<span class="number">8080</span>).sync().channel();</span><br><span class="line">            channel.closeFuture().sync();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            boss.shutdownGracefully();</span><br><span class="line">            worker.shutdownGracefully();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//客户端</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChatClient</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">group</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="type">MessageCodecSharable</span> <span class="variable">MESSAGE_CODEC</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MessageCodecSharable</span>();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Bootstrap</span> <span class="variable">bootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Bootstrap</span>();</span><br><span class="line">            bootstrap.channel(NioSocketChannel.class);</span><br><span class="line">            bootstrap.group(group);</span><br><span class="line">            bootstrap.handler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ProcotolFrameDecoder</span>());</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">MessageCodecSharable</span>());</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="type">Channel</span> <span class="variable">channel</span> <span class="operator">=</span> bootstrap.connect(<span class="string">&quot;localhost&quot;</span>, <span class="number">8080</span>).sync().channel();</span><br><span class="line">            channel.closeFuture().sync();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            group.shutdownGracefully();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProcotolFrameDecoder</span> <span class="keyword">extends</span> <span class="title class_">LengthFieldBasedFrameDecoder</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ProcotolFrameDecoder</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>(<span class="number">1024</span>, <span class="number">12</span>, <span class="number">4</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ProcotolFrameDecoder</span><span class="params">(<span class="type">int</span> maxFrameLength, <span class="type">int</span> lengthFieldOffset, <span class="type">int</span> lengthFieldLength, <span class="type">int</span> lengthAdjustment, <span class="type">int</span> initialBytesToStrip)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="会话管理"><a href="#会话管理" class="headerlink" title="会话管理"></a>会话管理</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 会话管理接口</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Session</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 绑定会话</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> channel 哪个 channel 要绑定会话</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> username 会话绑定用户</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">bind</span><span class="params">(Channel channel, String username)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 解绑会话</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> channel 哪个 channel 要解绑会话</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">unbind</span><span class="params">(Channel channel)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取属性</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> channel 哪个 channel</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> name 属性名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 属性值</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Object <span class="title function_">getAttribute</span><span class="params">(Channel channel, String name)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 设置属性</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> channel 哪个 channel</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> name 属性名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> value 属性值</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">setAttribute</span><span class="params">(Channel channel, String name, Object value)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 根据用户名获取 channel</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> username 用户名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> channel</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Channel <span class="title function_">getChannel</span><span class="params">(String username)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 聊天组会话管理接口</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">GroupSession</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 创建一个聊天组, 如果不存在才能创建成功, 否则返回 null</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> name 组名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> members 成员</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 成功时返回组对象, 失败返回 null</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Group <span class="title function_">createGroup</span><span class="params">(String name, Set&lt;String&gt; members)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 加入聊天组</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> name 组名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> member 成员名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 如果组不存在返回 null, 否则返回组对象</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Group <span class="title function_">joinMember</span><span class="params">(String name, String member)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 移除组成员</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> name 组名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> member 成员名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 如果组不存在返回 null, 否则返回组对象</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Group <span class="title function_">removeMember</span><span class="params">(String name, String member)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 移除聊天组</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> name 组名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 如果组不存在返回 null, 否则返回组对象</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Group <span class="title function_">removeGroup</span><span class="params">(String name)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取组成员</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> name 组名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 成员集合, 没有成员会返回 empty set</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Set&lt;String&gt; <span class="title function_">getMembers</span><span class="params">(String name)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取组成员的 channel 集合, 只有在线的 channel 才会返回</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> name 组名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 成员 channel 集合</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    List&lt;Channel&gt; <span class="title function_">getMembersChannel</span><span class="params">(String name)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 聊天组，即聊天室</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Group</span> &#123;</span><br><span class="line">    <span class="comment">// 聊天室名称</span></span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="comment">// 聊天室成员</span></span><br><span class="line">    <span class="keyword">private</span> Set&lt;String&gt; members;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Group</span> <span class="variable">EMPTY_GROUP</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Group</span>(<span class="string">&quot;empty&quot;</span>, Collections.emptySet());</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Group</span><span class="params">(String name, Set&lt;String&gt; members)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">        <span class="built_in">this</span>.members = members;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>实现类</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SessionMemoryImpl</span> <span class="keyword">implements</span> <span class="title class_">Session</span> &#123;</span><br><span class="line">    </span><br><span class="line">	<span class="comment">//简要的通过内存实现会话管理，两个map实现user和channel的互相查询</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, Channel&gt; usernameChannelMap = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;Channel, String&gt; channelUsernameMap = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;Channel,Map&lt;String,Object&gt;&gt; channelAttributesMap = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">bind</span><span class="params">(Channel channel, String username)</span> &#123;</span><br><span class="line">        usernameChannelMap.put(username, channel);</span><br><span class="line">        channelUsernameMap.put(channel, username);</span><br><span class="line">        channelAttributesMap.put(channel, <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unbind</span><span class="params">(Channel channel)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">username</span> <span class="operator">=</span> channelUsernameMap.remove(channel);</span><br><span class="line">        usernameChannelMap.remove(username);</span><br><span class="line">        channelAttributesMap.remove(channel);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">getAttribute</span><span class="params">(Channel channel, String name)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> channelAttributesMap.get(channel).get(name);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAttribute</span><span class="params">(Channel channel, String name, Object value)</span> &#123;</span><br><span class="line">        channelAttributesMap.get(channel).put(name, value);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Channel <span class="title function_">getChannel</span><span class="params">(String username)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> usernameChannelMap.get(username);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> usernameChannelMap.toString();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GroupSessionMemoryImpl</span> <span class="keyword">implements</span> <span class="title class_">GroupSession</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, Group&gt; groupMap = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Group <span class="title function_">createGroup</span><span class="params">(String name, Set&lt;String&gt; members)</span> &#123;</span><br><span class="line">        <span class="type">Group</span> <span class="variable">group</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Group</span>(name, members);</span><br><span class="line">        <span class="keyword">return</span> groupMap.putIfAbsent(name, group);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Group <span class="title function_">joinMember</span><span class="params">(String name, String member)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> groupMap.computeIfPresent(name, (key, value) -&gt; &#123;</span><br><span class="line">            value.getMembers().add(member);</span><br><span class="line">            <span class="keyword">return</span> value;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Group <span class="title function_">removeMember</span><span class="params">(String name, String member)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> groupMap.computeIfPresent(name, (key, value) -&gt; &#123;</span><br><span class="line">            value.getMembers().remove(member);</span><br><span class="line">            <span class="keyword">return</span> value;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Group <span class="title function_">removeGroup</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> groupMap.remove(name);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Set&lt;String&gt; <span class="title function_">getMembers</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> groupMap.getOrDefault(name, Group.EMPTY_GROUP).getMembers();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;Channel&gt; <span class="title function_">getMembersChannel</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> getMembers(name).stream()</span><br><span class="line">                .map(member -&gt; SessionFactory.getSession().getChannel(member))</span><br><span class="line">                .filter(Objects::nonNull)</span><br><span class="line">                .collect(Collectors.toList());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h4 id="相关类"><a href="#相关类" class="headerlink" title="相关类"></a>相关类</h4><p>消息类</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">Message</span> <span class="keyword">implements</span> <span class="title class_">Serializable</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Class&lt;?&gt; getMessageClass(<span class="type">int</span> messageType) &#123;</span><br><span class="line">        <span class="keyword">return</span> messageClasses.get(messageType);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> sequenceId;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> messageType;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="type">int</span> <span class="title function_">getMessageType</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">LoginRequestMessage</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">LoginResponseMessage</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">ChatRequestMessage</span> <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">ChatResponseMessage</span> <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">GroupCreateRequestMessage</span> <span class="operator">=</span> <span class="number">4</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">GroupCreateResponseMessage</span> <span class="operator">=</span> <span class="number">5</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">GroupJoinRequestMessage</span> <span class="operator">=</span> <span class="number">6</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">GroupJoinResponseMessage</span> <span class="operator">=</span> <span class="number">7</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">GroupQuitRequestMessage</span> <span class="operator">=</span> <span class="number">8</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">GroupQuitResponseMessage</span> <span class="operator">=</span> <span class="number">9</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">GroupChatRequestMessage</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">GroupChatResponseMessage</span> <span class="operator">=</span> <span class="number">11</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">GroupMembersRequestMessage</span> <span class="operator">=</span> <span class="number">12</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">GroupMembersResponseMessage</span> <span class="operator">=</span> <span class="number">13</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Map&lt;Integer, Class&lt;?&gt;&gt; messageClasses = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        messageClasses.put(LoginRequestMessage, LoginRequestMessage.class);</span><br><span class="line">        messageClasses.put(LoginResponseMessage, LoginResponseMessage.class);</span><br><span class="line">        messageClasses.put(ChatRequestMessage, ChatRequestMessage.class);</span><br><span class="line">        messageClasses.put(ChatResponseMessage, ChatResponseMessage.class);</span><br><span class="line">        messageClasses.put(GroupCreateRequestMessage, GroupCreateRequestMessage.class);</span><br><span class="line">        messageClasses.put(GroupCreateResponseMessage, GroupCreateResponseMessage.class);</span><br><span class="line">        messageClasses.put(GroupJoinRequestMessage, GroupJoinRequestMessage.class);</span><br><span class="line">        messageClasses.put(GroupJoinResponseMessage, GroupJoinResponseMessage.class);</span><br><span class="line">        messageClasses.put(GroupQuitRequestMessage, GroupQuitRequestMessage.class);</span><br><span class="line">        messageClasses.put(GroupQuitResponseMessage, GroupQuitResponseMessage.class);</span><br><span class="line">        messageClasses.put(GroupChatRequestMessage, GroupChatRequestMessage.class);</span><br><span class="line">        messageClasses.put(GroupChatResponseMessage, GroupChatResponseMessage.class);</span><br><span class="line">        messageClasses.put(GroupMembersRequestMessage, GroupMembersRequestMessage.class);</span><br><span class="line">        messageClasses.put(GroupMembersResponseMessage, GroupMembersResponseMessage.class);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>聊天请求</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">@Data</span><br><span class="line">@ToString(callSuper = true)</span><br><span class="line">public class ChatRequestMessage extends Message &#123;</span><br><span class="line">    private String content;</span><br><span class="line">    private String to;</span><br><span class="line">    private String from;</span><br><span class="line"></span><br><span class="line">    public ChatRequestMessage() &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public ChatRequestMessage(String from, String to, String content) &#123;</span><br><span class="line">        this.from = from;</span><br><span class="line">        this.to = to;</span><br><span class="line">        this.content = content;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    public int getMessageType() &#123;</span><br><span class="line">        return ChatRequestMessage;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>聊天响应</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">Data</span><br><span class="line"><span class="meta">@ToString(callSuper = true)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">AbstractResponseMessage</span> <span class="keyword">extends</span> <span class="title class_">Message</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> success;</span><br><span class="line">    <span class="keyword">private</span> String reason;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">AbstractResponseMessage</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">AbstractResponseMessage</span><span class="params">(<span class="type">boolean</span> success, String reason)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.success = success;</span><br><span class="line">        <span class="built_in">this</span>.reason = reason;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@ToString(callSuper = true)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChatResponseMessage</span> <span class="keyword">extends</span> <span class="title class_">AbstractResponseMessage</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> String from;</span><br><span class="line">    <span class="keyword">private</span> String content;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ChatResponseMessage</span><span class="params">(<span class="type">boolean</span> success, String reason)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(success, reason);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ChatResponseMessage</span><span class="params">(String from, String content)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.from = from;</span><br><span class="line">        <span class="built_in">this</span>.content = content;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getMessageType</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> ChatResponseMessage;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>除此之外，创建群，发送群消息，退出群都有对应的消息类</p>
<h4 id="用户管理"><a href="#用户管理" class="headerlink" title="用户管理"></a>用户管理</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 用户管理接口</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 登录</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> username 用户名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> password 密码</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 登录成功返回 true, 否则返回 false</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">login</span><span class="params">(String username, String password)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>写死了</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserServiceMemoryImpl</span> <span class="keyword">implements</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Map&lt;String, String&gt; allUserMap = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    &#123;</span><br><span class="line">        allUserMap.put(<span class="string">&quot;zhangsan&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        allUserMap.put(<span class="string">&quot;lisi&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        allUserMap.put(<span class="string">&quot;wangwu&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        allUserMap.put(<span class="string">&quot;zhaoliu&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        allUserMap.put(<span class="string">&quot;qianqi&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">login</span><span class="params">(String username, String password)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">pass</span> <span class="operator">=</span> allUserMap.get(username);</span><br><span class="line">        <span class="keyword">if</span> (pass == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> pass.equals(password);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="2-聊天室业务-登录"><a href="#2-聊天室业务-登录" class="headerlink" title="2 聊天室业务-登录"></a>2 聊天室业务-登录</h3><p>为使代码简洁，可进行重构，将new handler都提取出来，需要什么功能就加对应的handler</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//服务器端添加</span></span><br><span class="line">serverBootstrap.childHandler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ProcotolFrameDecoder</span>());</span><br><span class="line">        ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">MessageCodecSharable</span>());</span><br><span class="line">        ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LoginRequestMessageHandler</span>());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ChannelHandler</span>.Sharable</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LoginRequestMessageHandler</span> <span class="keyword">extends</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;LoginRequestMessage&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext channelHandlerContext, LoginRequestMessage msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">username</span> <span class="operator">=</span> msg.getUsername();</span><br><span class="line">        <span class="type">String</span> <span class="variable">password</span> <span class="operator">=</span> msg.getPassword();</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">login</span> <span class="operator">=</span> UserServiceFactory.getUserService().login(username, password);</span><br><span class="line">        LoginResponseMessage message;</span><br><span class="line">        <span class="keyword">if</span> (login) &#123;</span><br><span class="line">            SessionFactory.getSession().bind(channelHandlerContext.channel(), username);</span><br><span class="line">            message = <span class="keyword">new</span> <span class="title class_">LoginResponseMessage</span>(<span class="literal">true</span>, <span class="string">&quot;登录成功&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            message = <span class="keyword">new</span> <span class="title class_">LoginResponseMessage</span>(<span class="literal">false</span>, <span class="string">&quot;登录失败&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        channelHandlerContext.writeAndFlush(message);</span><br><span class="line">        System.out.println(message);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>客户端由于登录后需要进一步的接受指令，所以主线程和接受登录响应消息的线程之间需要通信，通过CountDownLatch实现</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">CountDownLatch WAIT_FOR_LOGIN = new CountDownLatch(1);</span><br><span class="line">AtomicBoolean LOGIN = new AtomicBoolean(false);//用来判断是否登录成功</span><br></pre></td></tr></table></figure>

<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br></pre></td><td class="code"><pre><span class="line">//客户端添加</span><br><span class="line">ch.pipeline().addLast(&quot;client handler&quot;, new ChannelInboundHandlerAdapter() &#123;</span><br><span class="line">    // 接收响应消息</span><br><span class="line">    @Override</span><br><span class="line">    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception &#123;</span><br><span class="line">        System.out.println(msg);</span><br><span class="line">        if (msg instanceof LoginResponseMessage) &#123;</span><br><span class="line">            LoginResponseMessage response = (LoginResponseMessage) msg;</span><br><span class="line">            if (response.isSuccess()) &#123;</span><br><span class="line">                LOGIN.set(true);</span><br><span class="line">            &#125;</span><br><span class="line">            //登录成功，唤醒System.in线程</span><br><span class="line">            WAIT_FOR_LOGIN.countDown();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    // 在连接建立后触发 active 事件</span><br><span class="line">    @Override</span><br><span class="line">    public void channelActive(ChannelHandlerContext ctx) throws Exception &#123;</span><br><span class="line">        // 负责接收用户在控制台的输入，负责向服务器发送各种消息</span><br><span class="line">        new Thread(() -&gt; &#123;</span><br><span class="line">            System.out.println(&quot;请输入用户名:&quot;);</span><br><span class="line">            String username = scanner.nextLine();</span><br><span class="line"></span><br><span class="line">            System.out.println(&quot;请输入密码:&quot;);</span><br><span class="line">            String password = scanner.nextLine();</span><br><span class="line">            // 构造消息对象</span><br><span class="line">            LoginRequestMessage message = new LoginRequestMessage(username, password);</span><br><span class="line">            System.out.println(message);</span><br><span class="line">            // 发送消息</span><br><span class="line">            ctx.writeAndFlush(message);</span><br><span class="line">            System.out.println(&quot;等待后续操作...&quot;);</span><br><span class="line">            try &#123;</span><br><span class="line">                WAIT_FOR_LOGIN.await();</span><br><span class="line">            &#125; catch (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">            //登录失败</span><br><span class="line">            if (!LOGIN.get()) &#123;</span><br><span class="line">                ctx.channel().close();</span><br><span class="line">                return;</span><br><span class="line">            &#125;</span><br><span class="line">            while (true)&#123;</span><br><span class="line">                System.out.println(&quot;==================================&quot;);</span><br><span class="line">                System.out.println(&quot;send [username] [content]&quot;);</span><br><span class="line">                System.out.println(&quot;gsend [group name] [content]&quot;);</span><br><span class="line">                System.out.println(&quot;gcreate [group name] [m1,m2,m3...]&quot;);</span><br><span class="line">                System.out.println(&quot;gmembers [group name]&quot;);</span><br><span class="line">                System.out.println(&quot;gjoin [group name]&quot;);</span><br><span class="line">                System.out.println(&quot;gquit [group name]&quot;);</span><br><span class="line">                System.out.println(&quot;quit&quot;);</span><br><span class="line">                System.out.println(&quot;==================================&quot;);</span><br><span class="line">                String command = scanner.nextLine();</span><br><span class="line">                String[] s = command.split(&quot; &quot;);</span><br><span class="line">                switch (s[0])&#123;</span><br><span class="line">                    case &quot;send&quot;:</span><br><span class="line">                        ctx.writeAndFlush(new ChatRequestMessage(username, s[1], s[2]));</span><br><span class="line">                        break;</span><br><span class="line">                    case &quot;gsend&quot;:</span><br><span class="line">                        ctx.writeAndFlush(new GroupChatRequestMessage(username, s[1], s[2]));</span><br><span class="line">                        break;</span><br><span class="line">                    case &quot;gcreate&quot;:</span><br><span class="line">                        Set&lt;String&gt; set = new HashSet&lt;&gt;(Arrays.asList(s[2].split(&quot;,&quot;)));</span><br><span class="line">                        set.add(username); // 加入自己</span><br><span class="line">                        ctx.writeAndFlush(new GroupCreateRequestMessage(s[1], set));</span><br><span class="line">                        break;</span><br><span class="line">                    case &quot;gmembers&quot;:</span><br><span class="line">                        ctx.writeAndFlush(new GroupMembersRequestMessage(s[1]));</span><br><span class="line">                        break;</span><br><span class="line">                    case &quot;gjoin&quot;:</span><br><span class="line">                        ctx.writeAndFlush(new GroupJoinRequestMessage(username, s[1]));</span><br><span class="line">                        break;</span><br><span class="line">                    case &quot;gquit&quot;:</span><br><span class="line">                        ctx.writeAndFlush(new GroupQuitRequestMessage(username, s[1]));</span><br><span class="line">                        break;</span><br><span class="line">                    case &quot;quit&quot;:</span><br><span class="line">                        ctx.channel().close();</span><br><span class="line">                        return;</span><br><span class="line">                &#125;</span><br><span class="line">     &#125;</span><br><span class="line">        &#125;, &quot;system in&quot;).start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>



<h3 id="3-聊天室业务-单聊"><a href="#3-聊天室业务-单聊" class="headerlink" title="3 聊天室业务-单聊"></a>3 聊天室业务-单聊</h3><p>单聊 handler</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ChannelHandler</span>.Sharable</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChatRequestMessageHandler</span> <span class="keyword">extends</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;ChatRequestMessage&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext ctx, ChatRequestMessage msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">to</span> <span class="operator">=</span> msg.getTo();</span><br><span class="line">        <span class="type">Channel</span> <span class="variable">channel</span> <span class="operator">=</span> SessionFactory.getSession().getChannel(to);</span><br><span class="line">        <span class="comment">// 在线</span></span><br><span class="line">        <span class="keyword">if</span>(channel != <span class="literal">null</span>) &#123;</span><br><span class="line">            channel.writeAndFlush(<span class="keyword">new</span> <span class="title class_">ChatResponseMessage</span>(msg.getFrom(), msg.getContent()));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 不在线</span></span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">ChatResponseMessage</span>(<span class="literal">false</span>, <span class="string">&quot;对方用户不存在或者不在线&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="4-聊天室业务-群聊"><a href="#4-聊天室业务-群聊" class="headerlink" title="4 聊天室业务-群聊"></a>4 聊天室业务-群聊</h3><p>创建群聊</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ChannelHandler</span>.Sharable</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GroupCreateRequestMessageHandler</span> <span class="keyword">extends</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;GroupCreateRequestMessage&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext ctx, GroupCreateRequestMessage msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">groupName</span> <span class="operator">=</span> msg.getGroupName();</span><br><span class="line">        Set&lt;String&gt; members = msg.getMembers();</span><br><span class="line">        <span class="comment">// 群管理器</span></span><br><span class="line">        <span class="type">GroupSession</span> <span class="variable">groupSession</span> <span class="operator">=</span> GroupSessionFactory.getGroupSession();</span><br><span class="line">        <span class="type">Group</span> <span class="variable">group</span> <span class="operator">=</span> groupSession.createGroup(groupName, members);</span><br><span class="line">        <span class="keyword">if</span> (group == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 发生成功消息</span></span><br><span class="line">            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupCreateResponseMessage</span>(<span class="literal">true</span>, groupName + <span class="string">&quot;创建成功&quot;</span>));</span><br><span class="line">            <span class="comment">// 发送拉群消息</span></span><br><span class="line">            List&lt;Channel&gt; channels = groupSession.getMembersChannel(groupName);</span><br><span class="line">            <span class="keyword">for</span> (Channel channel : channels) &#123;</span><br><span class="line">                channel.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupCreateResponseMessage</span>(<span class="literal">true</span>, <span class="string">&quot;您已被拉入&quot;</span> + groupName));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupCreateResponseMessage</span>(<span class="literal">false</span>, groupName + <span class="string">&quot;已经存在&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>群聊</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ChannelHandler</span>.Sharable</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GroupChatRequestMessageHandler</span> <span class="keyword">extends</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;GroupChatRequestMessage&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext ctx, GroupChatRequestMessage msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        List&lt;Channel&gt; channels = GroupSessionFactory.getGroupSession()</span><br><span class="line">                .getMembersChannel(msg.getGroupName());</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (Channel channel : channels) &#123;</span><br><span class="line">            channel.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupChatResponseMessage</span>(msg.getFrom(), msg.getContent()));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>加入群聊</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ChannelHandler</span>.Sharable</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GroupJoinRequestMessageHandler</span> <span class="keyword">extends</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;GroupJoinRequestMessage&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext ctx, GroupJoinRequestMessage msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">Group</span> <span class="variable">group</span> <span class="operator">=</span> GroupSessionFactory.getGroupSession().joinMember(msg.getGroupName(), msg.getUsername());</span><br><span class="line">        <span class="keyword">if</span> (group != <span class="literal">null</span>) &#123;</span><br><span class="line">            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupJoinResponseMessage</span>(<span class="literal">true</span>, msg.getGroupName() + <span class="string">&quot;群加入成功&quot;</span>));</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupJoinResponseMessage</span>(<span class="literal">true</span>, msg.getGroupName() + <span class="string">&quot;群不存在&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>退出群聊</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ChannelHandler</span>.Sharable</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GroupQuitRequestMessageHandler</span> <span class="keyword">extends</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;GroupQuitRequestMessage&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext ctx, GroupQuitRequestMessage msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">Group</span> <span class="variable">group</span> <span class="operator">=</span> GroupSessionFactory.getGroupSession().removeMember(msg.getGroupName(), msg.getUsername());</span><br><span class="line">        <span class="keyword">if</span> (group != <span class="literal">null</span>) &#123;</span><br><span class="line">            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupJoinResponseMessage</span>(<span class="literal">true</span>, <span class="string">&quot;已退出群&quot;</span> + msg.getGroupName()));</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupJoinResponseMessage</span>(<span class="literal">true</span>, msg.getGroupName() + <span class="string">&quot;群不存在&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>查看成员</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ChannelHandler</span>.Sharable</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GroupMembersRequestMessageHandler</span> <span class="keyword">extends</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;GroupMembersRequestMessage&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext ctx, GroupMembersRequestMessage msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        Set&lt;String&gt; members = GroupSessionFactory.getGroupSession()</span><br><span class="line">                .getMembers(msg.getGroupName());</span><br><span class="line">        ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupMembersResponseMessage</span>(members));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="5-聊天室业务-退出"><a href="#5-聊天室业务-退出" class="headerlink" title="5 聊天室业务-退出"></a>5 聊天室业务-退出</h3><p>客户端的退出分为正常退出和异常退出</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">@ChannelHandler.Sharable</span><br><span class="line">public class QuitHandler extends ChannelInboundHandlerAdapter &#123;</span><br><span class="line"></span><br><span class="line">    // 当连接断开时触发 inactive 事件</span><br><span class="line">    @Override</span><br><span class="line">    public void channelInactive(ChannelHandlerContext ctx) throws Exception &#123;</span><br><span class="line">        SessionFactory.getSession().unbind(ctx.channel());</span><br><span class="line">        System.out.println(ctx.channel().toString() + &quot;已经断开&quot;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    // 当出现异常时触发</span><br><span class="line">    @Override</span><br><span class="line">    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception &#123;</span><br><span class="line">        SessionFactory.getSession().unbind(ctx.channel());</span><br><span class="line">        System.out.println(ctx.channel().toString() + &quot;已经异常断开,&quot; +  &quot;异常是&quot; + cause.getMessage());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="6-聊天室业务-空闲检测"><a href="#6-聊天室业务-空闲检测" class="headerlink" title="6 聊天室业务-空闲检测"></a>6 聊天室业务-空闲检测</h3><h4 id="连接假死"><a href="#连接假死" class="headerlink" title="连接假死"></a>连接假死</h4><p>原因</p>
<ul>
<li>网络设备出现故障，例如网卡，机房等，底层的 TCP 连接已经断开了，但应用程序没有感知到，仍然占用着资源。</li>
<li>公网网络不稳定，出现丢包。如果连续出现丢包，这时现象就是客户端数据发不出去，服务端也一直收不到数据，就这么一直耗着</li>
<li>应用程序线程阻塞，无法进行数据读写</li>
</ul>
<p>问题</p>
<ul>
<li>假死的连接占用的资源不能自动释放</li>
<li>向假死的连接发送数据，得到的反馈是发送超时</li>
</ul>
<p>服务器端解决</p>
<ul>
<li>怎么判断客户端连接是否假死呢？如果能收到客户端数据，说明没有假死。因此策略就可以定为，每隔一段时间就检查这段时间内是否接收到客户端数据，没有就可以判定为连接假死</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 用来判断是不是 读空闲时间过长，或 写空闲时间过长</span></span><br><span class="line"><span class="comment">// 5s 内如果没有收到 channel 的数据，会触发一个 IdleState#READER_IDLE 事件</span></span><br><span class="line">ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">IdleStateHandler</span>(<span class="number">5</span>, <span class="number">0</span>, <span class="number">0</span>));</span><br><span class="line"><span class="comment">// ChannelDuplexHandler 可以同时作为入站和出站处理器</span></span><br><span class="line">ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelDuplexHandler</span>() &#123;</span><br><span class="line">    <span class="comment">// 用来触发特殊事件</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">userEventTriggered</span><span class="params">(ChannelHandlerContext ctx, Object evt)</span> <span class="keyword">throws</span> Exception&#123;</span><br><span class="line">        <span class="type">IdleStateEvent</span> <span class="variable">event</span> <span class="operator">=</span> (IdleStateEvent) evt;</span><br><span class="line">        <span class="comment">// 触发了读空闲事件</span></span><br><span class="line">        <span class="keyword">if</span> (event.state() == IdleState.READER_IDLE) &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;已经 5s 没有读到数据了&quot;</span>);</span><br><span class="line">            ctx.channel().close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>客户端定时心跳</p>
<ul>
<li>客户端可以定时向服务器端发送数据，只要这个时间间隔小于服务器定义的空闲检测的时间间隔，那么就能防止前面提到的误判，客户端可以定义如下心跳处理器</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 用来判断是不是 读空闲时间过长，或 写空闲时间过长</span></span><br><span class="line"><span class="comment">// 3s 内如果没有向服务器写数据，会触发一个 IdleState#WRITER_IDLE 事件</span></span><br><span class="line">ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">IdleStateHandler</span>(<span class="number">0</span>, <span class="number">3</span>, <span class="number">0</span>));</span><br><span class="line"><span class="comment">// ChannelDuplexHandler 可以同时作为入站和出站处理器</span></span><br><span class="line">ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelDuplexHandler</span>() &#123;</span><br><span class="line">    <span class="comment">// 用来触发特殊事件</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">userEventTriggered</span><span class="params">(ChannelHandlerContext ctx, Object evt)</span> <span class="keyword">throws</span> Exception&#123;</span><br><span class="line">        <span class="type">IdleStateEvent</span> <span class="variable">event</span> <span class="operator">=</span> (IdleStateEvent) evt;</span><br><span class="line">        <span class="comment">// 触发了写空闲事件</span></span><br><span class="line">        <span class="keyword">if</span> (event.state() == IdleState.WRITER_IDLE) &#123;</span><br><span class="line">            <span class="comment">//System.out.println(&quot;3s 没有写数据了，发送一个心跳包&quot;);</span></span><br><span class="line">            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">PingMessage</span>());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>






      
    </div>
    <div class="article-footer">
      <blockquote class="mt-2x">
  <ul class="post-copyright list-unstyled">
    
    <li class="post-copyright-link hidden-xs">
      <strong>本文链接：</strong>
      <a href="https://skyung.gitee.io/Skyung/2022/05/01/netty%E8%81%8A%E5%A4%A9%E5%AE%A4/" title="Netty聊天室" target="_blank" rel="external">https://skyung.gitee.io/Skyung/2022/05/01/netty聊天室/</a>
    </li>
    
    <li class="post-copyright-license">
      <strong>版权声明： </strong> 本博客所有文章除特别声明外，均采用 <a href="http://creativecommons.org/licenses/by/4.0/deed.zh" target="_blank" rel="external">CC BY 4.0 CN协议</a> 许可协议。转载请注明出处！
    </li>
  </ul>
</blockquote>


<div class="panel panel-default panel-badger">
  <div class="panel-body">
    <figure class="media">
      <div class="media-left">
        <a href="https://github.com/cofess" target="_blank" class="img-burn thumb-sm visible-lg">
          <img src="/Skyung/images/avatar.jpg" class="img-rounded w-full" alt="">
        </a>
      </div>
      <div class="media-body">
        <h3 class="media-heading"><a href="https://github.com/cofess" target="_blank"><span class="text-dark">Skyung</span><small class="ml-1x">Web Developer</small></a></h3>
        <div>脚踏实地，仰望星空</div>
      </div>
    </figure>
  </div>
</div>


    </div>
  </article>
  
    
  <section id="comments">
  	
      <div id="uyan_frame"></div>
    
  </section>


  
</div>

  <nav class="bar bar-footer clearfix" data-stick-bottom>
  <div class="bar-inner">
  
  <ul class="pager pull-left">
    
    <li class="prev">
      <a href="/Skyung/2022/05/07/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/" title="单元测试"><i class="icon icon-angle-left" aria-hidden="true"></i><span>&nbsp;&nbsp;上一篇</span></a>
    </li>
    
    
    <li class="next">
      <a href="/Skyung/2022/04/24/NIO/" title="NIO"><span>下一篇&nbsp;&nbsp;</span><i class="icon icon-angle-right" aria-hidden="true"></i></a>
    </li>
    
    
    <li class="toggle-toc">
      <a class="toggle-btn " data-toggle="collapse" href="#collapseToc" aria-expanded="false" title="文章目录" role="button">    <span>[&nbsp;</span><span>文章目录</span>
        <i class="text-collapsed icon icon-anchor"></i>
        <i class="text-in icon icon-close"></i>
        <span>]</span>
      </a>
    </li>
    
  </ul>
  
  
  <!-- Button trigger modal -->
  <button type="button" class="btn btn-fancy btn-donate pop-onhover bg-gradient-warning" data-toggle="modal" data-target="#donateModal"><span>赏</span></button>
  <!-- <div class="wave-icon wave-icon-danger btn-donate" data-toggle="modal" data-target="#donateModal">
    <div class="wave-circle"><span class="icon"><i class="icon icon-bill"></i></span></div>
  </div> -->
  
  
  <div class="bar-right">
    
    <div class="share-component" data-sites="weibo,qq,wechat,facebook,twitter" data-mobile-sites="weibo,qq,qzone"></div>
    
  </div>
  </div>
</nav>
  
<!-- Modal -->
<div class="modal modal-center modal-small modal-xs-full fade" id="donateModal" tabindex="-1" role="dialog">
  <div class="modal-dialog" role="document">
    <div class="modal-content donate">
      <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
      <div class="modal-body">
        <div class="donate-box">
          <div class="donate-head">
            <p>感谢您的支持，我会继续努力的!</p>
          </div>
          <div class="tab-content">
            <div role="tabpanel" class="tab-pane fade active in" id="alipay">
              <div class="donate-payimg">
                <img src="/Skyung/images/donate/alipay.jpg" alt="扫码支持" title="扫一扫" />
              </div>
              <p class="text-muted mv">扫码打赏，你说多少就多少</p>
              <p class="text-grey">打开支付宝扫一扫，即可进行扫码打赏哦</p>
            </div>
            <div role="tabpanel" class="tab-pane fade" id="wechatpay">
              <div class="donate-payimg">
                <img src="/Skyung/images/donate/wechat.jpg" alt="扫码支持" title="扫一扫" />
              </div>
              <p class="text-muted mv">扫码打赏，你说多少就多少</p>
              <p class="text-grey">打开微信扫一扫，即可进行扫码打赏哦</p>
            </div>
          </div>
          <div class="donate-footer">
            <ul class="nav nav-tabs nav-justified" role="tablist">
              <li role="presentation" class="active">
                <a href="#alipay" id="alipay-tab" role="tab" data-toggle="tab" aria-controls="alipay" aria-expanded="true"><i class="icon icon-alipay"></i> 支付宝</a>
              </li>
              <li role="presentation" class="">
                <a href="#wechatpay" role="tab" id="wechatpay-tab" data-toggle="tab" aria-controls="wechatpay" aria-expanded="false"><i class="icon icon-wepay"></i> 微信支付</a>
              </li>
            </ul>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>



</main>

  <footer class="footer" itemscope itemtype="http://schema.org/WPFooter">
	
	
    <ul class="social-links">
    	
        <li><a href="/Skyung/null" target="_blank" title="Github" data-toggle=tooltip data-placement=top><i class="icon icon-github"></i></a></li>
        
        <li><a href="/Skyung/null" target="_blank" title="Weibo" data-toggle=tooltip data-placement=top><i class="icon icon-weibo"></i></a></li>
        
        <li><a href="/Skyung/null" target="_blank" title="Twitter" data-toggle=tooltip data-placement=top><i class="icon icon-twitter"></i></a></li>
        
        <li><a href="/Skyung/null" target="_blank" title="Behance" data-toggle=tooltip data-placement=top><i class="icon icon-behance"></i></a></li>
        
        <li><a href="/Skyung/atom.xml" target="_blank" title="Rss" data-toggle=tooltip data-placement=top><i class="icon icon-rss"></i></a></li>
        
    </ul>

    <div class="copyright">
    	
        <div class="publishby">
        	Theme by <a href="https://github.com/cofess" target="_blank"> cofess </a>base on <a href="https://github.com/cofess/hexo-theme-pure" target="_blank">pure</a>.
        </div>
    </div>
</footer>
  <script src="//cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
<script>
window.jQuery || document.write('<script src="js/jquery.min.js"><\/script>')
</script>

<script src="/Skyung/js/plugin.min.js"></script>


<script src="/Skyung/js/application.js"></script>


    <script>
(function (window) {
    var INSIGHT_CONFIG = {
        TRANSLATION: {
            POSTS: '文章',
            PAGES: '页面',
            CATEGORIES: '分类',
            TAGS: '标签',
            UNTITLED: '(未命名)',
        },
        ROOT_URL: '/Skyung/',
        CONTENT_URL: '/Skyung/content.json',
    };
    window.INSIGHT_CONFIG = INSIGHT_CONFIG;
})(window);
</script>

<script src="/Skyung/js/insight.js"></script>






   




   
    
    <script defer type="text/javascript" src="http://v2.uyan.cc/code/uyan.js?uid=[object Object]"></script>








</body>
</html>